/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.event.impl;

import cgl.narada.event.DistributionTraces;
import cgl.narada.event.EventHeaders;
import cgl.narada.event.EventID;
import cgl.narada.event.NBEvent;
import cgl.narada.service.ServiceException;
import cgl.narada.service.security.impl.EntityOperationsImpl;
import cgl.narada.service.time.TimeService;
import cgl.narada.service.time.ntptime.TimeServiceImpl;
import cgl.narada.service.timer.ClockI;
import cgl.narada.service.timer.HRClock;

/**
 * This is the class which is responsible for the generation of NBEvents.
 * 
 * @author Shrideep Pallickara $Date$
 * $Revision$
 */

public class NBEventGenerator {

    private EventIDGenerator eventIdGenerator;
    private String moduleName = "NBEventGenerator: ";
    private TimeService tsi;
    private static int tracker = 0;
    private ClockI hrClock;

    public NBEventGenerator() {
        eventIdGenerator = new EventIDGenerator();
        tsi = TimeServiceImpl.getInstance();
    }

    public NBEvent updateTimestampInfo(NBEvent nbEvent, long publishTimestamp) {
        NBEventImpl nbEventImpl = (NBEventImpl) nbEvent;
        EventHeadersImpl headers = (EventHeadersImpl) nbEventImpl
                .getEventHeaders();
        headers.setTimestamp(publishTimestamp);
        return nbEventImpl;
    }

    public NBEvent updateContentSynopsisInfo(NBEvent nbEvent,
            int contentSynopsisType, Object contentSynopsis) {
        NBEventImpl nbEventImpl = (NBEventImpl) nbEvent;
        nbEventImpl
                .setContentSynopsisInfo(contentSynopsisType, contentSynopsis);
        return nbEventImpl;
    }

    public NBEvent generateEvent(int templateId, int source,
            boolean suppressDistToSource, boolean timestamp,
            boolean useHRtimestamp, boolean useNtpTimestamp, int timeToLive,
            boolean generateId, boolean isCorrelated, EventID correlationId,
            boolean persistent, int priority, int contentSynopsisType,
            Object contentSynopsis, byte[] contentPayload)
            throws ServiceException {

        EventHeadersImpl headers = new EventHeadersImpl();

        headers.setTemplateInfo(templateId);

        headers.setSourceInformation(source, suppressDistToSource);

        if (timestamp) {
            if (useNtpTimestamp) {
                headers.setTimestamp(tsi.getTimestamp());
            } else {
                if (useHRtimestamp) {
                    if (hrClock == null) {
                        /** initialize hrclock, first time it is used */
                        hrClock = HRClock.getClock();
                    }
                    headers.setTimestamp(hrClock.getTimeMilliseconds());
                } else {
                    headers.setTimestamp(System.currentTimeMillis());
                }
            }
        } /* if (timestamp) */

        headers.setTimeToLive(timeToLive);

        if (isCorrelated) {
            if (correlationId != null) {
                headers.setCorrelationId(correlationId);
            } else {
                throw new ServiceException(
                        "Specified correlation identifier = NULL");
            }
        } /* end correlation processing */

        if (persistent) {
            headers.setPersistent();
        }

        headers.setPriorityInformation(priority);

        if (contentSynopsis == null) {
            throw new ServiceException("Specified content synopsis = NULL");
        }

        if (contentPayload == null) {
            throw new ServiceException("Specified content payload = NULL");
        }

        NBEventImpl nbEvent = new NBEventImpl();
        nbEvent.setContentSynopsisInfo(contentSynopsisType, contentSynopsis);
        nbEvent.setContentPayload(contentPayload);

        if (generateId) {
            if (source == 0) {
                throw new ServiceException("Specified entity id is 0"
                        + "Generation of event id is not possible");
            }
            headers.setEventId(eventIdGenerator.generateEventId(source));
        }

        nbEvent.setEventHeaders(headers);

        return nbEvent;
    }

    public void updateEncryptedPayloadInformation(NBEvent nbEvent,
            byte[] securePayload, byte[] cipherIV, String cipherMode,
            String cipherPaddingScheme) {
        NBEventImpl event = (NBEventImpl) nbEvent;
        event.setContentPayload(securePayload);

        EventHeadersImpl headers = (EventHeadersImpl) nbEvent.getEventHeaders();
        headers.setEncryptedPayloadInformation(cipherIV, cipherMode,
                cipherPaddingScheme);
    }

    public void updateSignatureInformation(NBEvent nbEvent,
            String signatureAlgorithm, byte[] signature) {
        EventHeadersImpl headers = (EventHeadersImpl) nbEvent.getEventHeaders();
        headers.setSignatureInformation(signatureAlgorithm, signature);
    }

    /** This method is used to update security information for an event */
    public void updateAssertionsInformation(NBEvent nbEvent, String assertions) {
        EventHeadersImpl headers = (EventHeadersImpl) nbEvent.getEventHeaders();
        headers.setAssertionsInformation(assertions);
    }

    /** This method is used to update integrity information for an event */
    public static void updateIntegrityInformation(NBEvent nbEvent,
            String digestScheme, byte[] messageDigest) {
        EventHeadersImpl headers = (EventHeadersImpl) nbEvent.getEventHeaders();
        headers.setIntegrityInformation(digestScheme, messageDigest);
    }

    /**
     * This method is used to create a fragmented event from the information
     * supplied by the fragmentation service.
     */
    public NBEvent getFragmentedEvent(NBEvent nbEvent, String filename,
            long originalLength, byte[] fragmentedPayload, int fragmentNumber,
            int totalNumberOfFragments, long fragmentationEpoch)
            throws ServiceException {
        if (!nbEvent.hasEventHeaders()) {
            throw new ServiceException(moduleName
                    + "Event does not have event headers ");
        }

        EventHeadersImpl headers = (EventHeadersImpl) nbEvent.getEventHeaders();

        int source = headers.getSource();

        if (source == 0) {
            throw new ServiceException(moduleName + "Need source information");
        }

        EventHeadersImpl newHeaders = new EventHeadersImpl(headers.getBytes());

        EventID newIdForFragment = eventIdGenerator.generateEventId(source);
        newHeaders.setEventId(newIdForFragment);

        /**
         * The fragmentation identifier for the fragmented event, is the eventID
         * of the original event
         */
        newHeaders.setFragmentationInformation(filename, originalLength,
                fragmentNumber, totalNumberOfFragments, fragmentationEpoch);

        int eventType = nbEvent.getEventType();
        int contentSynopsisType = nbEvent.getContentSynopsisType();
        Object contentSynopsis = nbEvent.getContentSynopsis();

        /** Construct the fragmented event */
        NBEventImpl nbFragmentEvent = new NBEventImpl();
        nbFragmentEvent.setEventType(eventType);
        nbFragmentEvent.setEventHeaders(newHeaders);
        nbFragmentEvent.setContentSynopsisInfo(contentSynopsisType,
                contentSynopsis);

        nbFragmentEvent.setContentPayload(fragmentedPayload);
        nbFragmentEvent.setDistributionTraces(null);

        return nbFragmentEvent;
    }

    /**
     * This method is used to create a coalesced event from the information
     * supplied by the coalescing service.
     */
    public NBEvent getCoalescedEvent(NBEvent fragmentEvent,
            byte[] coalescedPayload) throws ServiceException {
        if (!fragmentEvent.hasEventHeaders()) {
            throw new ServiceException(moduleName
                    + "Event does not have event headers ");
        }

        EventHeadersImpl headers = (EventHeadersImpl) fragmentEvent
                .getEventHeaders();

        String fragmentationIdentifier = headers.getFragmentationIdentifier();

        if (fragmentationIdentifier == null) {
            throw new ServiceException(moduleName
                    + "A Non-Fragmented event was"
                    + " supplied for generating coalesced event");
        }

        EventHeadersImpl newHeaders = new EventHeadersImpl(headers.getBytes());

        int eventType = fragmentEvent.getEventType();
        int contentSynopsisType = fragmentEvent.getContentSynopsisType();
        Object contentSynopsis = fragmentEvent.getContentSynopsis();
        DistributionTraces traces = fragmentEvent.getDistributionTraces();

        NBEventImpl nbCoalescedEvent = new NBEventImpl();
        nbCoalescedEvent.setEventType(eventType);
        nbCoalescedEvent.setEventHeaders(newHeaders);
        nbCoalescedEvent.setContentSynopsisInfo(contentSynopsisType,
                contentSynopsis);

        nbCoalescedEvent.setContentPayload(coalescedPayload);

        if (traces != null) {
            nbCoalescedEvent.setDistributionTraces(traces);
        }

        return nbCoalescedEvent;

    }

    /** This method is used to update slice information for an event */
    public void updateSlicedInformation(NBEvent nbEvent,
            int sliceStartingPoint, int sliceEndingPoint) {
        EventHeadersImpl headers = (EventHeadersImpl) nbEvent.getEventHeaders();
        headers.setSlicedInformation(sliceStartingPoint, sliceEndingPoint);

    }

    /** This method is used to update compression information for an event */
    public void updateCompressionInformation(NBEvent nbEvent,
            String compressionAlgo, long originalPayloadLength,
            boolean isPayloadCompressionFinalStep, byte[] compressedPayload) {
        EventHeadersImpl headers = (EventHeadersImpl) nbEvent.getEventHeaders();

        headers.setPayloadCompressionInformation(compressionAlgo,
                originalPayloadLength, isPayloadCompressionFinalStep);
        NBEventImpl event = (NBEventImpl) nbEvent;
        event.setContentPayload(compressedPayload);

    }

    public void updateWithDecompressedPayload(NBEvent nbEvent,
            byte[] decompressedPayload) {
        NBEventImpl event = (NBEventImpl) nbEvent;
        event.setContentPayload(decompressedPayload);        
    }

    // HG: Security stuff
    
    public void updateWithEncryptedPayload(NBEvent nbEvent,
             byte[] encryptedPayload) {
         NBEventImpl event = (NBEventImpl) nbEvent;
         event.setContentPayload(encryptedPayload);         
     }

    public void updateWithDecryptedPayload(NBEvent nbEvent,
             byte[] decryptedPayload) {
         NBEventImpl event = (NBEventImpl) nbEvent;
         event.setContentPayload(decryptedPayload);
     }

    // ---
    
    public void setPayload(NBEvent nbEvent, byte[] newPayload) {
        NBEventImpl event = (NBEventImpl) nbEvent;
        event.setContentPayload(newPayload);
    }

    public static void updateDistributionTraces(NBEvent nbEvent,
            DistributionTraces traces) {
        if (traces != null) {
            ((NBEventImpl) nbEvent).setDistributionTraces(traces);
        }
    }

    public static void setEventType(NBEvent nbEvent, int eventType) {
        ((NBEventImpl) nbEvent).setEventType(eventType);
        ;
    }

    public static NBEvent unmarshallEvent(byte[] nbEventBytes) {
        NBEventImpl nbEvent = new NBEventImpl(nbEventBytes);
        return nbEvent;
    }

    public static void validateIntegrityInformation(NBEvent nbEvent) {
        try {
            EntityOperationsImpl entityOperations = new EntityOperationsImpl();
            String digestScheme = "SHA";
            byte[] payload = nbEvent.getContentPayload();

            EventHeaders headers = nbEvent.getEventHeaders();

            if (!headers.hasIntegrity()) {
                System.out.println("No integrity information available");
                return;
            }

            byte[] digest = headers.getMessageDigest();
            boolean digestVerify = entityOperations.validateMessageDigest(
                    digest, payload, digestScheme);

            if (digestVerify) {
                // System.out.print(".");
            } else {
                tracker++;
                System.out.print("" + tracker);
            }
        } catch (ServiceException serEx) {
            System.out.println(serEx);
        }
    }

}
